﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Windows.Forms;
using Finance.TodoSoft.UIControls;

namespace Finance.TodoSoft.Controls
{
	public partial class ListView : Panel
	{
		public event EventHandler<EventArgs> SelectedItemChanged;
		public event EventHandler<ListItemEventArgs> ItemDoubleClick;

		public ListView()
		{
			_controlViewTypes = new Dictionary<Type, ItemViewType>();
			_itemViewTypes = new Dictionary<Type, ItemViewType>();
			InitializeComponent();
		}

		//Item type -> ItemViewType
		private readonly Dictionary<Type, ItemViewType> _itemViewTypes;
		//Control type -> ItemViewType
		private readonly Dictionary<Type, ItemViewType> _controlViewTypes;

		public void AddViewType(Type itemType, Type viewType)
		{
			var itemViewType = new ItemViewType(viewType);
			_itemViewTypes.Add(itemType, itemViewType);
			_controlViewTypes.Add(viewType, itemViewType);
		}


		private object _items;
		private Control _selectedItemView;
		private object _selectedItem;

		public object Items
		{
			set
			{
				if (_items == value)
					return;
				_items = value;
				ItemsUpdated();
			}
		}

		private void ItemsUpdated()
		{
			Controls.Clear();

			if (_items == null)
				return;

			if (_notifiedList != null)
				_notifiedList.ListChanged -= OnNotifiedListChanged;

			var list = _items as IEnumerable;
			if (list != null)
			{
				foreach (var item in list)
				{
					AddItem(item);
				}

				_notifiedList = list as IBindingList;
				if(_notifiedList != null)
					_notifiedList.ListChanged += OnNotifiedListChanged;
			}
		}

		private void AddItem(object item)
		{
			if (!_itemViewTypes.ContainsKey(item.GetType()))
				return;

			var itemViewType = _itemViewTypes[item.GetType()];

			var control = CreateItemViewControl(itemViewType);

			itemViewType.SetData(control, item);

			Controls.Add(control);

			control.BringToFront();
		}

		void OnNotifiedListChanged(object sender, ListChangedEventArgs e)
		{
			switch (e.ListChangedType)
			{
				case ListChangedType.ItemAdded:
					AddItem(_notifiedList[e.NewIndex]);
					break;
				case ListChangedType.Reset:
					Controls.Clear();
					break;
				case ListChangedType.ItemDeleted:
					Controls.RemoveAt(Controls.Count - 1 - e.NewIndex);
					break;
			}
		}

		public object SelectedItem
		{
			get { return _selectedItem; }
			set
			{
				if (_selectedItem == value)
					return;

				_selectedItem = value;

				if (SelectedItemChanged != null)
					SelectedItemChanged(this, EventArgs.Empty);
			}
		}

		private Control CreateItemViewControl(ItemViewType itemViewType)
		{
			var control = itemViewType.CreateControl();
			
			control.Dock = DockStyle.Top;
			control.MouseUp += OnItemControlMouseUp;
			control.MouseDown += OnItemControlMouseDown;

			if (control.ContextMenu == null)
				control.ContextMenu = ContextMenu;
			
			return control;
		}

		private int _previousMouseUpTicks;
		private object _previousMouseUpSender;
		private IBindingList _notifiedList;

		private void OnItemControlMouseUp(object sender, MouseEventArgs e)
		{
			int now = Environment.TickCount;

			// A double-click is detected if the the time elapsed
			// since the last click is within DoubleClickTime.
			if (now - _previousMouseUpTicks <= SystemInformation.DoubleClickTime
			    && _previousMouseUpSender == sender)
			{
				// Raise the DoubleClick event.
				OnItemControlDoubleClick(sender);
			}

			// Set previousClick to now so that 
			// subsequent double-clicks can be detected.
			_previousMouseUpTicks = now;
			_previousMouseUpSender = sender;

			// Allow the base class to raise the regular Click event.
			base.OnClick(e);
		}

		private void SelectItemView(Control control)
		{
			if (_selectedItemView != null)
			{
				_controlViewTypes[_selectedItemView.GetType()].Select(_selectedItemView, false);	
			}

			_selectedItemView = control;

			if (_selectedItemView == null)
				return;

			_controlViewTypes[control.GetType()].Select(control, true);
                       
			SelectedItem = GetItemViewData(_selectedItemView);
		}

		private object GetItemViewData(Control control)
		{
			var viewType = _controlViewTypes[control.GetType()];

			return viewType.GetData(control);
		}

		private void OnItemControlMouseDown(object sender, MouseEventArgs e)
		{
			SelectItemView((Control)sender);
		}

		void OnItemControlDoubleClick(object sender)
		{
			var item = GetItemViewData((Control)sender);

			if (item == null)
				return;

			ItemDoubleClick.InvokeIfNotNull(this, new ListItemEventArgs { Item = item });
		}

		class ItemViewType
		{
			private readonly Type _type;
			private readonly MethodInfo _itemDataSetter;
			private readonly MethodInfo _itemDataGetter;
			private readonly MethodInfo _itemSelectedSetter;

			public ItemViewType(Type type)
			{
				_type = type;

				var dataItemProperty = _type.GetProperty("Data");
				if (dataItemProperty == null)
					return;
                
				_itemDataSetter = dataItemProperty.GetSetMethod();
				_itemDataGetter = dataItemProperty.GetGetMethod();

				var selectedItemProperty = _type.GetProperty("Selected");
				if (selectedItemProperty != null)
				{
					_itemSelectedSetter = selectedItemProperty.GetSetMethod();
				}
			}

			public Control CreateControl()
			{
				var control =  (Control)Activator.CreateInstance(_type);
				return control;
			}

			public void SetData(Control control, object item)
			{
				_itemDataSetter.Invoke(control, new[] { item });
			}

			public object GetData(Control control)
			{
				return _itemDataGetter.Invoke(control, new object[] {});
			}

			public void Select(Control control, bool select)
			{
				if (_itemSelectedSetter == null)
					return;

				_itemSelectedSetter.Invoke(control, new object[] { select });
			}
		}
	}
}

namespace Finance.TodoSoft.UIControls
{
	public class ListItemEventArgs:EventArgs
	{
		public object Item;
	}
}
